package net;


import interfaces.Activator;
import interfaces.Normalizer;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;

import util.Matrix;
import enums.ReadMode;
import exceptions.IncompatibleSampleException;
import exceptions.InvalidSubstitionRuleException;

public abstract class NeuralNet {
	protected LinkedList<Neuron> inputLayer, outputLayer,hiddenLayer;
	protected LinkedList<Sample> samples; 
	protected LinkedList<Class> classes ;
	protected LinkedList<Substitution> substitutions;
	protected ReadMode readMode;
	protected Normalizer normalizer;
	protected Activator activator;
	protected Number[] lastVerifiedOutput;
	protected String delimiterString;
	protected Number[] maxValues,minValues;
	protected Number maxValue,minValue;
	protected double learningRate;
	protected double tolerance;


	public NeuralNet(int inputNeurons,int hiddenNeurons,int outputNeurons) {
		inputLayer = new LinkedList<Neuron>();
		outputLayer = new LinkedList<Neuron>();
		samples = new LinkedList<Sample>();
		substitutions = new LinkedList<Substitution>();
		classes = new LinkedList<Class>();
		delimiterString = ";";
		normalizer = new Normalizer(){
			@Override
			public Number doNormalize(Number x) {
				return x;
			}
		};		
		activator = new Activator() {			
			@Override
			public Number doActivation(Number x) {
				return x.doubleValue() >= 0?1:-1;
			}

			@Override
			public Number functionDerived(Number x) {
				// TODO Auto-generated method stub
				return x;
			}

		};
		for (int i = 0; i < inputNeurons; i++) {
			inputLayer.add(new Neuron(activator));
		}
		for (int i = 0; i < outputNeurons; i++) {
			if (hiddenNeurons > 0){
				outputLayer.add(new Neuron(activator,new Bias()));
			} else{
				outputLayer.add(new Neuron(activator));
			}
		}
		if(hiddenNeurons == 0){
			hiddenLayer = null;
			for (Neuron n: outputLayer){
				for (Neuron n2:inputLayer){
					n.addSynapse(n2);
				}
			}
		}else{
			hiddenLayer = new LinkedList<Neuron>();
			for(int i=0;i< hiddenNeurons;i++){
				hiddenLayer.add(new Neuron(activator, new Bias()));
			}
			for(Neuron n:outputLayer){
				for(Neuron n2:hiddenLayer){
					n.addSynapse(n2);
					for(Neuron n3:inputLayer){
						n2.addSynapse(n3);
					}
				}
			}
		}		
		readMode = ReadMode.CHAR_MODE;
		maxValues = null;
		minValues = null;
		maxValue = Double.MIN_VALUE;
		minValue = Double.MAX_VALUE;
	}


	public NeuralNet(int neuroniosEntrada,int neuroniosSaida) {
		this(neuroniosEntrada,0,neuroniosSaida);
	}


	public Class addClasse(String file,String description){
		return addClasse(readFile(file), description);
	}

	public LinkedList<Synapse> getSynapses(){
		LinkedList<Synapse> aux = new LinkedList<Synapse>();
		for(Neuron n: outputLayer){
			for(Synapse s:n.getInputList()){
				aux.add(s);
			}
		}
		for(Neuron n: hiddenLayer){
			for(Synapse s:n.getInputList()){
				aux.add(s);
			}
		}
		return aux;
	}



	public Class addClasse(Number[] output,String description){
		Class c = new Class(output, description);
		classes.add(c);
		return c;
	}

	public void normalizeSamples(){

	}	

	public Normalizer getNormalizer() {
		return normalizer;
	}

	protected Number normalize(Number x){
		return normalizer.doNormalize(x);
	}

	public void setNormalizer(Normalizer normalizer) {
	}

	public void setSubstitutionRules(char[] inputs, Number[] values) throws IllegalArgumentException{
		if(inputs.length != values.length){
			throw new InvalidSubstitionRuleException();
		}
		for(int i = 0;i<inputs.length;i++){
			addSubstituicao(inputs[i],values[i]);
		}
	}

	public Activator getActivationFunction() {
		return activator;
	}

	public void setActivationFunction(Activator activationFunction){
		for(Neuron n:outputLayer){
			n.setActivator(activationFunction);
		}
	}

	protected Number[] readFile(String file){
		ArrayList<Number> input = new ArrayList<Number>();
		try {
			BufferedReader br = new BufferedReader(new FileReader(new File(file)));
			String line;
			if (readMode == ReadMode.CHAR_MODE){
				while((line = br.readLine())!= null){
					char[] caracteres = line.toCharArray();
					for(char a:caracteres){
						boolean found = false;
						for(Substitution subs:substitutions){
							if(subs.getInput() == a){
								input.add(subs.getOutput());
								found = true;
								break;
							}
						}
						if (!found){
							input.add(0);
						}
					}
				}
			}else {
				while((line = br.readLine())!= null){
					String[] caracteristicas = line.split(delimiterString);
					for(String a:caracteristicas){
						input.add(Double.parseDouble(a+""));
					}
				}
			}
			br.close();
			Number[] saida = new Number[input.size()];
			saida = input.toArray(saida);
			return saida;
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				

		return null;
	}

	protected void applyActivationFunction(Number[] input){
		for(int i=0;i<input.length;i++){
			input[i] = activator.doActivation(input[i]);
		}
	}

	public void setNeuronInputs(Number[] input){
		for (int i = 0; i < input.length; i++) {
			inputLayer.get(i).setOutput(input[i]);			
		}
	}

	public void setNeuronInputs(String file){
		setNeuronInputs(readFile(file));
	}

	public void addSubstituicao(char entrada, Number saida){
		substitutions.add(new Substitution(entrada, saida));
	}



	public Class analize(Number[][] input){
		return analize(input[0]);
	}

	public Class analize(Number[] input){
		setNeuronInputs(input);
		Number[] output = new Number[outputLayer.size()];
		int i =0;
		if(hiddenLayer != null){
			for(Neuron n: hiddenLayer){
				n.calculateOutput();
			}
		}
		for(Neuron n: outputLayer){
			output[i] = (n.calculateOutput());
			i++;
		}
		lastVerifiedOutput = output;

		//                System.out.println(output[0]);
		//                System.out.println(output[1]);
		//                
		return verificaClasse(output);
	}

	public LinkedList<Neuron> getCamadaEntrada() {
		return inputLayer;
	}


	public LinkedList<Neuron> getCamadaSaida() {
		return outputLayer;
	}

	public void addSample(String arquivo,Class classe){
		addSample(arquivo, classe, "");
	}

	public void addSample(Number[] input,Class classe){
		Sample s = new Sample(input, classe);
		samples.add(s);
	}

	public void addSample(String arquivo,Class classe,String description){		
		Sample s = new Sample(readFile(arquivo),classe );
		s.setDescription(description);
		samples.add(s);
		if (!classes.contains(classe)){
			classes.add(classe);
		}
		if(maxValues == null){
			maxValues = new Number[s.getInput().length];
			minValues = new Number[s.getInput().length];
			for (int i = 0; i < maxValues.length; i++) {
				Number x = s.getInput()[i];
				maxValues[i] = x;
				minValues[i] = x;
				replaceIfBigger(x, maxValue);
				replaceIfSmaller(x, minValue);
			}
		}else{
			if(s.getInput().length != maxValues.length){
				throw new IncompatibleSampleException();
			}else{
				for (int i = 0; i < s.getInput().length; i++) {
					Number x = s.getInput()[i];
					replaceIfBigger(x, maxValue);
					replaceIfSmaller(x, minValue);
					replaceIfBigger(x, maxValues[i]);
					replaceIfSmaller(x, minValues[i]);
				}
			}
		}
	}

	protected Class verificaClasse(Number[][] saida){
		return verificaClasse(saida[0]);
	}

	protected Class verificaClasse(Number[] saida){	
		for (Class c:classes){
			boolean equals = true;
			for(int i=0;i<saida.length;i++){
				if (c.getOutput()[i].doubleValue() != saida[i].doubleValue()){
					equals = false;
					break;
				}
			}
			if(equals){
				return c;
			}
		}
		return null;
	}

	protected void setWeights(Number[][] w){
		for(int j=0;j < Matrix.columns(w);j++){
			Neuron n = outputLayer.get(j);
			for(int i=0;i < Matrix.lines(w);i++){
				n.getInputList().get(i).setWeight(w[i][j]);
			}
		}
	}

	public LinkedList<Substitution> getSubstitutions() {
		return substitutions;
	}

	public void setSubstituicoes(LinkedList<Substitution> substituicoes) {
		this.substitutions = substituicoes;
	}

	public LinkedList<Sample> getSamples() {
		return samples;
	}

	public void setSamples(
			LinkedList<Sample> arquivosTreinamento) {
		this.samples = arquivosTreinamento;
	}

	public LinkedList<Class> getClasses() {
		return classes;
	}

	public void setClasses(LinkedList<Class> classes) {
		this.classes = classes;
	}

	public ReadMode getReadMode() {
		return readMode;
	}

	public void setReadMode(ReadMode readMode) {
		this.readMode = readMode;
	}

	public void setInputLayer(LinkedList<Neuron> camadaEntrada) {
		this.inputLayer = camadaEntrada;
	}

	public void setOutputLayer(LinkedList<Neuron> camadaSaida) {
		this.outputLayer = camadaSaida;
	}
	public Activator getActivator() {
		return activator;
	}
	public void setActivator(Activator activator) {
		this.activator = activator;
		for(Neuron n:outputLayer){
			n.setActivator(activator);
		}
	}
	public Number[] getLastVerifiedOutput() {
		return lastVerifiedOutput;
	}
	public LinkedList<Neuron> getInputLayer() {
		return inputLayer;
	}
	public LinkedList<Neuron> getOutputLayer() {
		return outputLayer;
	}
	public void setSubstitutions(LinkedList<Substitution> substitutions) {
		this.substitutions = substitutions;
	}


	public LinkedList<Neuron> getHiddenLayer() {
		return hiddenLayer;
	}




	public void setHiddenLayer(LinkedList<Neuron> hiddenLayer) {
		this.hiddenLayer = hiddenLayer;
	}


	public String getDelimiterString() {
		return delimiterString;
	}


	public void setDelimiterString(String delimiterString) {
		this.delimiterString = delimiterString;
	}


	public void setLastVerifiedOutput(Number[] lastVerifiedOutput) {
		this.lastVerifiedOutput = lastVerifiedOutput;
	}

	protected void replaceIfBigger(Number source,Number destination){
		replace(source, destination, source.doubleValue() > destination.doubleValue());
	}

	protected void replaceIfSmaller(Number source,Number destination){
		replace(source, destination, source.doubleValue() < destination.doubleValue());
	}

	private void replace(Number source, Number destination, boolean condition){
		if(condition){
			destination = source;
		}
	}


	public double getLearningRate() {
		return learningRate;
	}


	public void setLearningRate(double learningRate) {
		this.learningRate = learningRate;
	}


}
